/////////////////////////////////////////////////////////////////////////////////
///              Clase que bufferea el Video
class VideoBuffer {

  int FramesPerBuffer = 40;
  int currentFrame;
  int lastShown;
  int speed = 1;

  ArrayList<PImage> buffer1;
  ArrayList<PImage> buffer2;

  ArrayList<PImage> buffer;
  int initBuffer;
  ArrayList<PImage> preFetch;
  int initAux;

  BuffLoader loader;
  PImage alt;

  Boolean play;
  Boolean pause = false;

  ////////////////////////////////////////////////////
  // CONST
  VideoBuffer (int frameInicial, PImage alternative) {    

    buffer1 = new ArrayList<PImage>(FramesPerBuffer);
    buffer2= new ArrayList<PImage>(FramesPerBuffer);

    PImage aux;
    alt = alternative;

    loader = new BuffLoader(buffer2, 0, FramesPerBuffer, alt);


    println ("========================");    
    println ("inicilalizando el buffer");
    println ("========================");

    // inicialmente el buffer es 1
    buffer   = buffer1;
    preFetch = buffer2;

    // realiza la carga inicial
    initBuffer = frameInicial- (FramesPerBuffer/2);
    for (int i = 0; i < FramesPerBuffer; i++) {
      int loadFrame = i + initBuffer;    
      //println ("load frame:" + loadFrame + " text:" + int2Frame(loadFrame));
      aux = loadImage(path + int2Frame(loadFrame) + FileExtension);
      if (aux != null)
        buffer.add (aux);
      else {
        buffer.add(alt);
      }
    }

  //  println ("cargados del " + initBuffer + " a " + (initBuffer+FramesPerBuffer-1));
    currentFrame = frameInicial;
    lastShown = currentFrame;
  //  println ("buffer Ready & pointing @ " +  currentFrame);
  //  println ("========================");
    play = false;
  }


  synchronized PImage getFrame() {  
    lastShown = currentFrame;  

    // are we in bounds??
    if ((currentFrame >= initBuffer) && (currentFrame < initBuffer+FramesPerBuffer)) {
      if (buffer.size() == 0) {
        println ("ERROR, buffer vacio, o recargando");
        return alt;
      }

      return buffer.get(currentFrame-initBuffer);
    }
    else {
      print ("ERROR: pedido " + currentFrame + " limites: [" + initBuffer + "," + (initBuffer+FramesPerBuffer-1)+ "] ");
      println  ("[" + initAux + "," + (initAux+FramesPerBuffer-1) + "]");
    }
    println ("ERROR, algo fue mal");

    return null;
  }

  synchronized private void swap() {
    synchronized(loader) {
   //   println ("  --- SWAPPING ---");
      ArrayList<PImage> aux = buffer;
      buffer = preFetch;
      preFetch = aux;
      initBuffer  = initAux;
      //      println ("actual de " + initBuffer +" a " + (initBuffer+FramesPerBuffer));
    }
  }

  void togglePlay() {
    play = !play;
  }

  synchronized void moveForward(int numFrames) {
    synchronized(loader) {
      play = false;
      currentFrame += numFrames;
      println (" >>  " + currentFrame);
//      if (currentFrame == initBuffer+FramesPerBuffer-1)
//        println (" end buffer");
//      else
//        println ("");        
      checkBuffers();
    }
  }
  synchronized void moveBackward(int numFrames) {
    synchronized(loader) {
      play = false;
      if (currentFrame == initBuffer)
        gotoFrame (currentFrame);
      else{
        currentFrame -= numFrames;    
        println (" <<  " + currentFrame);
//        if (currentFrame == initBuffer)
//          println (" start buffer");
//        else
//          println (""); 
        checkBuffers();
      }
    }
  }


  synchronized  void gotoFrame(int queryFrame) {
    play = false; 

    loader = null;
    initAux = queryFrame-(FramesPerBuffer/2);
    loader = new BuffLoader(preFetch, initAux, FramesPerBuffer, alt);
    loader.start();

    try {
      loader.join();
    }
    catch (InterruptedException e) {
      println("Join interrupted");
    }
    this.swap();
    currentFrame = queryFrame;
    lastShown =queryFrame;
  }

  int getFrameNumber () {
    return currentFrame;
  }

  synchronized void updateVideo() {
    if (play && (!pause)) {
      currentFrame += speed;
      checkBuffers();
    }
  }

  synchronized void togglePause(){
    pause = !pause;
  }

  synchronized void checkBuffers() {
    if (currentFrame < lastShown) {
      // moving backward, prefetch previous frames
      if (initAux != initBuffer-FramesPerBuffer) {
        initAux =  initBuffer-FramesPerBuffer; 
        loader = null;
        loader = new BuffLoader(preFetch, initAux, FramesPerBuffer, alt);
        loader.start();
      }
    }
    else {
      // moving forward, prefech next frames
      if (initAux != initBuffer+FramesPerBuffer) {
        initAux = initBuffer+FramesPerBuffer;
        loader = null;
        loader = new BuffLoader(preFetch, initAux, FramesPerBuffer, alt);
        loader.start();
      }
    }

    if (currentFrame >= initBuffer+FramesPerBuffer) // move foeward
      this.swap();
    if (currentFrame < initBuffer)
      this.swap();
  }
}

class BuffLoader extends Thread {

  ArrayList<PImage> target;
  int startFrame;
  int FramesPerBuffer;

  PImage alt;

  BuffLoader (ArrayList<PImage> t, int start, int fpb, PImage alternative) {
    target = t;
    startFrame = start;
    FramesPerBuffer = fpb;
    alt = alternative;
  }

  public void run() {
    synchronized(this) { 

//      println ("Prefetch: " + startFrame + " to " + (startFrame+FramesPerBuffer));
      target.clear();
      PImage aux;
      for (int i = 0; i < FramesPerBuffer; i++) {
        int loadFrame = i + startFrame;
        aux  = loadImage(path+int2Frame(loadFrame)+FileExtension);
        if (aux != null)
          target.add (aux);
        else
          target.add (alt);
      }
      //      println ("Prefetch done ");
    }
  }
}

